refactor: Decouple ModelFacade from LiteLLM via ModelClient adapter#373
refactor: Decouple ModelFacade from LiteLLM via ModelClient adapter#373
Conversation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…provements - Wrap all LiteLLM router calls in try/except to normalize raw exceptions into canonical ProviderError at the bridge boundary (blocking review item) - Extract reusable response-parsing helpers into clients/parsing.py for shared use across future native adapters - Add async image parsing path using httpx.AsyncClient to avoid blocking the event loop in agenerate_image - Add retry_after field to ProviderError for future retry engine support - Fix _to_int_or_none to parse numeric strings from providers - Create test conftest.py with shared mock_router/bridge_client fixtures - Parametrize duplicate image generation and error mapping tests - Add tests for exception wrapping across all bridge methods
…larity - Parse RFC 7231 HTTP-date strings in Retry-After header (used by Azure and Anthropic during rate-limiting) in addition to numeric delay-seconds - Clarify collect_non_none_optional_fields docstring explaining why f.default is None is the correct check for optional field forwarding - Add tests for HTTP-date and garbage Retry-After values
- Fix misleading comment about prompt field defaults in _IMAGE_EXCLUDE - Handle list-format detail arrays in _extract_structured_message for FastAPI/Pydantic validation errors - Document scope boundary for vision content in collect_raw_image_candidates
…el-facade-guts-pr2
…el-facade-guts-pr2
- Replace @DataClass + __post_init__ with explicit __init__ that calls super().__init__ properly, avoiding brittle field-ordering dependency - Store cause via __cause__ only, removing the redundant .cause attr - Update match pattern in handle_llm_exceptions for non-dataclass type - Rename shadowed local `fields` to `optional_fields` in TransportKwargs
Greptile SummaryThis PR decouples Key changes:
Issues found:
|
📋 Summary
Decouples
ModelFacadefrom direct LiteLLM router usage by introducing aModelClientadapter layer. The facade now operates entirely on canonical request/response types (ChatCompletionRequest,ChatCompletionResponse, etc.) instead of raw LiteLLM objects, making it testable without LiteLLM and preparing for future client backends.This is PR 2 of the model facade overhaul series, building on the canonical types and
LiteLLMBridgeClientintroduced in PR 1.🔄 Changes
✨ Added
clients/factory.py—create_model_client()factory that handles provider resolution, API key setup, and LiteLLM router constructionTransportKwargs— Unified transport preparation that flattensextra_bodyinto top-level kwargs and separatesextra_headers_raise_from_provider_error()inerrors.py— Maps canonicalProviderErrortoDataDesignerErrorsubclassesextract_message_from_exception_string()for parsing human-readable messages from stringified LiteLLM exceptionsmake_stub_completion_response()test helper for creating canonical test fixturesclose()/aclose()lifecycle methods onModelFacadeandModelRegistrytest_parsing.pyforTransportKwargsbehavior🔧 Changed
ModelFacade— Now accepts aModelClientvia constructor injection instead of creating its ownCustomRouter. All methods use canonical types (ChatCompletionRequest/Response,EmbeddingRequest/Response,ImageGenerationRequest/Response)MCPFacade— Operates on canonicalChatCompletionResponseandToolCalltypes instead of raw LiteLLM response objects; removed internal tool call normalization (_extract_tool_calls,_normalize_tool_call) since parsing now happens in the client layerLiteLLMBridgeClient— UsesTransportKwargs.from_request()instead ofcollect_non_none_optional_fields()for cleaner request forwardingProviderError— Refactored from@dataclassto regularExceptionsubclass for proper exception semantics_track_usage()operating on canonicalUsagetypeStubResponse/StubMessage/FakeResponse/FakeMessage; tests mockModelClientinstead ofCustomRoutermodel_facade_factory— Now creates aModelClientfirst, then injects it intoModelFacade🗑️ Removed
_try_extract_base64()and direct image parsing fromModelFacade(moved to client layer in PR1)_get_litellm_deployment()fromModelFacade(moved tocreate_model_client())collect_non_none_optional_fields()fromparsing.py(replaced byTransportKwargs)_track_token_usage_from_completion,_track_token_usage_from_embedding,_track_token_usage_from_image_diffusion) replaced by unified_track_usage()StubResponse/FakeResponseusage in tests (replaced by canonical types)🔍 Attention Areas
facade.py— Core refactor: constructor signature change (clientreplacessecret_resolver+ internal router), all methods now use canonical typeserrors.py— New_raise_from_provider_error()mapping function andProviderError→Exceptionrefactortypes.py—TransportKwargsdesign: flatteningextra_bodyvs keepingextra_headersseparatemcp/facade.py— Significant simplification from canonical types; verify the_convert_canonical_tool_calls_to_dictsbridge is correct🤖 Generated with Claude Code